<template>
	<el-tree-select
		style="width: 100%"
		v-model="value"
		v-bind="attrs"
		:id="'c' + columnId"
		:name="'c' + columnId"
		:filterable="filterable"
		:clearable="clearable"
		:remote="remote"
		:remote-method="remoteMethod"
		:teleported="false"
		@focus="focusMethod"
		@blur="blurMethod"
		ref="treeRef"
		:data="treeList"
		:show-checkbox="showCheckbox"
		:render-after-expand="renderAfterExpand"
		:node-key="nodeKey"
		:draggable="draggable"
		:props="theProps"
		:indent="indent"
		:load="loadNode"
		:render-content="renderContent"
		:highlight-current="highlightCurrent"
		:default-expand-all="defaultExpandAll"
		:expand-on-click-node="expandOnClickNode"
		:check-on-click-node="checkOnClickNode"
		:auto-expand-parent="autoExpandParent"
		:default-expanded-keys="theExpandedKeys"
		:check-strictly="checkStrictly"
		:default-checked-keys="theSelectedKeys"
		:current-node-key="currentNodeKey"
		:filter-node-method="filterNode"
		:accordion="accordion"
		:icon="icon"
		:lazy="lazy"
		:allow-drag="allowDrag"
		:allow-drop="allowDrop"
		@node-drop="onNodeDrop"
		@node-click="onNodeClick"
		@node-contextmenu="onNodeContextmenu"
		@check-change="onCheckChange"
		@check="onCheck"
		@current-change="onCurrentChange"
		@node-expand="onNodeExpand"
		@node-collapse="onNodeCollapse"
		@node-drag-start="onNodeDragStart"
		@node-drag-enter="onNodeDragEnter"
		@node-drag-leave="onNodeDragLeave"
		@node-drag-over="onNodeDragOver"
		@node-drag-end="onNodeDragEnd"
	>
		<template v-slot:default="{ node }">
			<element-tree-line :node="node" :showLabelLine="showLabelLine" :indent="indent">
				<template v-slot:node-label>
					<span :style="{ fontSize: fontSize }" v-if="searchValue && node.label.indexOf(searchValue) > -1">
						{{ node.label.substr(0, node.label.indexOf(searchValue)) }}
						<span class="search-tree-color">{{ searchValue }}</span>
						{{ node.label.substr(node.label.indexOf(searchValue) + searchValue.length) }}
					</span>
					<span v-else :style="{ fontSize: fontSize }">
						{{ node.label }}
					</span>
				</template>
			</element-tree-line>
		</template>
	</el-tree-select>
</template>
<script lang="ts" setup name="zh-select-tree">
import type { ElTreeSelect } from 'element-plus';
import { getElementLabelLine } from '@/components/zh-tree/zh-tree-line';
import type Node from 'element-plus/es/components/tree/src/model/node';
import { loopExpandKeys } from '@/utils/util';
// 引入组件需要的属性 引入表单子控件的管理类
import { itemProps, itemController, selectProps, initSelect } from '../lib/mapController';
let ElementTreeLine = getElementLabelLine(h);
//------------------接收传入参数(组件)-------------------
// 定义属性
const prop = defineProps({
	...itemProps, // 基础属性
	...selectProps,
	modelValue: {
		type: [Number, String, Boolean, Array],
		default: '',
	},
	maxMeight: [String, Number], //滚动条最大高度
	height: [String, Number], //滚动条高度
	minSize: Number, //滚动条最小尺寸
	data: Array, //展示数据
	emptyText: String, //内容为空的时候展示的文本
	nodeKey: String, //每个树节点用来作为唯一标识的属性，整棵树应该是唯一的
	props: {
		//配置选项
		type: Object,
		default: function () {
			return {
				label: 'label',
				children: 'children',
				disabled: 'disabled',
				isLeaf: 'isLeaf',
				tag: 'tagName',
			};
		},
	},
	draggable: {
		//是否开启拖拽节点功能
		type: Boolean,
		default: false,
	},
	filterable: {
		//是否开启过滤
		type: Boolean,
		default: true,
	},
	showSearch: {
		//是否显示查询框
		type: Boolean,
		default: true,
	},
	showCheckbox: {
		//节点是否可被选择
		type: Boolean,
		default: false,
	},
	nodeLevel: {
		//默认查询的节点级别
		type: Number,
		default: null,
	},
	renderAfterExpand: {
		//是否在第一次展开某个树节点后才渲染其子节点
		type: Boolean,
		default: true,
	},
	indent: {
		//相邻级节点间的水平缩进，单位为像素
		type: Number,
		default: 16,
	},
	load: Function, //加载子树数据的方法，仅当 lazy 属性为true 时生效
	renderContent: Function, //树节点的内容区的渲染 Function
	highlightCurrent: {
		//是否高亮当前选中节点，默认值是 true
		type: Boolean,
		default: true,
	},
	defaultExpandAll: {
		//是否高亮当前选中节点，默认值是 false
		type: Boolean,
		default: false,
	},
	expandOnClickNode: {
		//是否在点击节点的时候展开或者收缩节点， 默认值为 true，如果为 false，则只有点箭头图标的时候才会展开或者收缩节点。
		type: Boolean,
		default: true,
	},
	checkOnClickNode: {
		//是否在点击节点的时候选中节点，默认值为 false，即只有在点击复选框时才会选中节点。
		type: Boolean,
		default: false,
	},
	autoExpandParent: {
		//展开子节点的时候是否自动展开父节点
		type: Boolean,
		default: true,
	},
	defaultExpandedKeys: Array, //默认展开的节点的 key 的数组
	checkStrictly: {
		//在显示复选框的情况下，是否严格的遵循父子不互相关联的做法，默认为 false
		type: Boolean,
		default: false,
	},
	defaultCheckedKeys: Array, //默认勾选的节点的 key 的数组
	currentNodeKey: [String, Number], //当前选中的节点
	filterNodeMethod: Function, //对树节点进行筛选时执行的方法， 返回 false 则表示这个节点会被隐藏
	accordion: {
		//是否每次只打开一个同级树节点展开
		type: Boolean,
		default: false,
	},
	icon: [String], //自定义图标组件
	lazy: {
		//是否懒加载子节点，需与 load 方法结合使用
		type: Boolean,
		default: false,
	},
	allowDrag: Function, //判断节点能否被拖拽 如果返回 false ，节点不能被拖动
	allowDrop: Function, //拖拽时判定目标节点能否成为拖动目标位置。 如果返回 false ，拖动节点不能被拖放到目标节点。 type 参数有三种情况：'prev'、'inner' 和 'next'，分别表示放置在目标节点前、插入至目标节点和放置在目标节点后
	url: String, //调用后台接口的url
	moveUrl: String, //移动节点url
	searchField: {
		//查询提交到后台字段
		type: String,
		default: 'searchText',
	},
	showLabelLine: {
		//是否显示 label 的横线
		type: Boolean,
		default: false,
	},
	resultFields: {
		//返回数据字段配置
		type: Object,
		default: function () {
			return { treeList: 'treeList', expandedKeys: 'expandedKeys', selectedKeys: 'selectedKeys' };
		},
	},
});
const attrs = useAttrs();
const allAttrs = computed(() => {
	return Object.assign(prop.extend, attrs);
});

//------------------定义子组件---------------------------

//------------------内部变量定义-------------------------
let theProps = ref({ label: 'label', children: 'children', disabled: 'disabled', isLeaf: 'isLeaf', tag: 'tagName' });
//------------------定义调用方法-------------------------

//获取全局字体大小
const fontSize = ref('12px');
let globalComponentSize = ref($store.theme.globalComponentSize);
onBeforeMount(() => {
	if (prop.props) {
		let myProps = Object.assign({ label: 'label', children: 'children', disabled: 'disabled', isLeaf: 'isLeaf', tag: 'tagName' }, prop.props);
		theProps.value = myProps;
	}
	if (globalComponentSize.value === 'small') {
		fontSize.value = '12px';
	} else if (globalComponentSize.value === 'default') {
		fontSize.value = '14px';
	} else if (globalComponentSize.value === 'large') {
		fontSize.value = '14px';
	}
});

const searchValue = ref('');

const treeList = ref(prop.data);
const theExpandedKeys = ref(prop.defaultExpandedKeys);
const theSelectedKeys = ref(prop.defaultCheckedKeys);
const treeRef = ref<InstanceType<typeof ElTreeSelect>>();
watch(
	() => prop.data,
	(val) => {
		treeList.value = val;
	}
);
watch(
	() => prop.defaultExpandedKeys,
	(val) => {
		theExpandedKeys.value = val;
	}
);
watch(
	() => prop.defaultCheckedKeys,
	(val) => {
		theSelectedKeys.value = val;
	}
);

//数据过滤
const filterNode = (value: string, data: any, node) => {
	console.log(node);
	if (prop.lazy && prop.url) {
		searchValue.value = value;
		query();
		return;
	}
	if (prop.filterNodeMethod) {
		return prop.filterNodeMethod(value, data);
	}
	if (!value) return true;
	return data[theProps.value.label].includes(value);
};
let resultFields = Object.assign({ treeList: 'treeList', expandedKeys: 'expandedKeys', selectedKeys: 'selectedKeys' }, prop.resultFields);
let level = ref(prop.nodeLevel);
if (prop.lazy && !level.value) {
	level.value = 2;
}
const loadNode = (node: Node, resolve) => {
	if (prop.url) {
		if (node.level === 0) {
			resolve(node.data);
		} else if (node.level < level.value) {
			resolve(node.data[theProps.value.children]);
		} else {
			if (node.data.loaded) {
				resolve(node.data[theProps.value.children] || []);
			} else {
				const params: any = {};
				params.parentKey = node.key;
				params.lazy = true;
				setTimeout(() => {
					$http
						.post(prop.url, params)
						.then((res: any) => {
							let result = res.result;
							const list = result[resultFields.treeList];
							resolve(list);
						})
						.catch(() => {
							resolve([]);
						});
				}, 50);
			}
		}
	} else {
		if (prop.load) {
			prop.load(node, resolve);
		}
	}
};
let query = () => {
	if (prop.url) {
		const params: any = {};
		params[prop.searchField] = searchValue.value;
		if (!searchValue.value) {
			params.nodeLevel = level.value;
		}

		setTimeout(() => {
			$http
				.post(prop.url, params)
				.then((res: any) => {
					let result = res.result;
					const list = result[resultFields.treeList];
					treeList.value = list;
					let expandedKeys = [];
					loopExpandKeys(list, expandedKeys);
					if (expandedKeys.length <= 0) {
						expandedKeys = result[resultFields.expandedKeys] || [];
					}
					theExpandedKeys.value = expandedKeys;
					let selectedKeys = result[resultFields.selectedKeys];
					theSelectedKeys.value = selectedKeys || [];
				})
				.catch(() => {});
		}, 50);
	}
};
const emits = defineEmits([
	'update:modelValue',
	'node-drop',
	'node-click',
	'node-contextmenu',
	'check-change',
	'check',
	'current-change',
	'node-expand',
	'node-collapse',
	'node-drag-start',
	'node-drag-enter',
	'node-drag-over',
	'node-drag-leave',
	'node-drag-end',
]);
const { value } = itemController(prop, allAttrs.value, emits);
const { remoteMethod, focusMethod, blurMethod } = initSelect(prop, value);
let onNodeDrop = (node: any, dropNode: any, position: string, event: any) => {
	const dropKey = dropNode.key; //目标节点key
	const dragKey = node.key; //被拖拽节点key
	let dropPosition = 0;
	if (position === 'before') {
		dropPosition = -1;
	} else if (position === 'after') {
		dropPosition = 1;
	} else if (position === 'inner') {
		dropPosition = 0;
	}
	const params = { dropKey: dropKey, dragKey: dragKey, dropPosition: dropPosition };
	if (prop.moveUrl) {
		setTimeout(() => {
			$http
				.post(prop.moveUrl, params)
				.then(() => {
					emits('node-drop', node, dropNode, position, event);
				})
				.catch(() => {
					//报错重新查询数据
					query();
				});
		}, 50);
	} else {
		emits('node-drop', node, dropNode, position, event);
	}
};
//当节点被点击的时候触发  三个参数：对应于节点点击的节点对象， TreeNode 节点 属性，事件对象
const onNodeClick = (...args: any[]) => {
	emits('node-click', ...args);
};
//当某一节点被鼠标右键点击时会触发该事件
//共四个参数，依次为：event、传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。
const onNodeContextmenu = (...args: any[]) => {
	emits('node-contextmenu', ...args);
};
//当复选框被点击的时候触发
//	共三个参数，依次为：传递给 data 属性的数组中该节点所对应的对象、节点本身是否被选中、节点的子树中是否有被选中的节点
const onCheckChange = (...args: any[]) => {
	emits('check-change', ...args);
};
//点击节点复选框之后触发
//共两个参数，依次为：传递给 data 属性的数组中该节点所对应的对象、树目前的选中状态对象，包含 checkedNodes、checkedKeys、halfCheckedNodes、halfCheckedKeys 四个属性
const onCheck = (...args: any[]) => {
	emits('check', ...args);
};
//当前选中节点变化时触发的事件
//共两个参数，依次为：当前节点的数据，当前节点的 Node 对象
const onCurrentChange = (...args: any[]) => {
	emits('current-change', ...args);
};
//节点被展开时触发的事件
//共三个参数，依次为：传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身
const onNodeExpand = (...args: any[]) => {
	emits('node-expand', ...args);
};
//节点被关闭时触发的事件
//共三个参数，依次为：传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身
const onNodeCollapse = (...args: any[]) => {
	emits('node-collapse', ...args);
};
//节点开始拖拽时触发的事件
//共两个参数，依次为：被拖拽节点对应的 Node、event
const onNodeDragStart = (...args: any[]) => {
	emits('node-drag-start', ...args);
};
//拖拽进入其他节点时触发的事件
//共三个参数，依次为：被拖拽节点对应的 Node、所进入节点对应的 Node、event
const onNodeDragEnter = (...args: any[]) => {
	emits('node-drag-enter', ...args);
};
//拖拽离开某个节点时触发的事件
//共三个参数，依次为：被拖拽节点对应的 Node、所离开节点对应的 Node、event
const onNodeDragLeave = (...args: any[]) => {
	emits('node-drag-leave', ...args);
};
//在拖拽节点时触发的事件（类似浏览器的 mouseover 事件）
//共三个参数，依次为：被拖拽节点对应的 Node、当前进入节点对应的 Node、event
const onNodeDragOver = (...args: any[]) => {
	emits('node-drag-over', ...args);
};
//拖拽结束时（可能未成功）触发的事件
//共四个参数，依次为：被拖拽节点对应的 Node、结束拖拽时最后进入的节点（可能为空）、被拖拽节点的放置位置（before、after、inner）、event
const onNodeDragEnd = (...args: any[]) => {
	emits('node-drag-end', ...args);
};
//-----------------------方法-------------------------------------------------//
//为节点设置新数据，只有当设置 node-key 属性的时候才可用
//(key, data) 接收两个参数: 1. 节点的 key 2. 新数据
const updateKeyChildren = (key: string | number, data: any) => {
	treeRef.value!.updateKeyChildren(key, data);
};
//如果节点可以被选中，(show-checkbox 为 true), 本方法将返回当前选中节点的数组
//(leafOnly, includeHalfChecked) 接收两个布尔类型参数: 1. 默认值为 false. 若参数为 true, 它将返回当前选中节点的子节点 2. 默认值为 false. 如果参数为 true, 返回值包含半选中节点数据
const getCheckedNodes = (leafOnly: false, includeHalfChecked = false) => {
	return treeRef.value!.getCheckedNodes(leafOnly, includeHalfChecked);
};
//设置目前勾选的节点，使用此方法必须提前设置 node-key 属性  要选中的节点构成的数组
const setCheckedNodes = (keys: []) => {
	treeRef.value!.setCheckedNodes(keys);
};
//若节点可用被选中 (show-checkbox 为 true), 它将返回当前选中节点 key 的数组
//(leafOnly) 接收一个布尔类型参数，默认为 false. 如果参数是 true, 它只返回当前选择的子节点数组。
const getCheckedKeys = (leafOnly: false) => {
	return treeRef.value!.getCheckedKeys(leafOnly);
};
//设置目前选中的节点，使用此方法必须设置 node-key 属性
//(keys, leafOnly) 接收两个参数: 1. 一个需要被选中的多节点 key 的数组 2. 一个布尔类型参数，默认为 false. 如果参数是 true, 它只返回当前选择的子节点数组。
const setCheckedKeys = (keys: [], leafOnly = false) => {
	treeRef.value!.setCheckedKeys(keys, leafOnly);
};
//设置节点是否被选中, 使用此方法必须设置 node-key 属性
//(key/data, checked, deep) 接收三个参数: 1. 要选中的节点的 key 或者数据 2. 一个布尔类型参数表明是否选中. 3. 一个布尔类型参数表明是否递归选中子节点
const setChecked = (key: string | number, checked: boolean, deep: boolean) => {
	treeRef.value!.setChecked(key, checked, deep);
};
//如果节点可用被选中 (show-checkbox 为 true), 它将返回当前半选中的节点组成的数组
const getHalfCheckedNodes = () => {
	return treeRef.value!.getHalfCheckedNodes();
};
//若节点可被选中(show-checkbox 为 true)，则返回目前半选中的节点的 key 所组成的数组
const getHalfCheckedKeys = () => {
	return treeRef.value!.getHalfCheckedKeys();
};
//返回当前被选中节点的数据 (如果没有则返回 null)
const getCurrentKey = () => {
	return treeRef.value!.getCurrentKey();
};
//返回当前被选中节点的数据 (如果没有则返回 null)
const getCurrentNode = () => {
	return treeRef.value!.getCurrentNode();
};
//通过 key 设置某个节点的当前选中状态，使用此方法必须设置 node-key  属性
//(key, shouldAutoExpandParent=true) 1. 待被选节点的 key， 如果为 null, 取消当前选中的节点 2. 是否自动展开父节点
const setCurrentKey = (key: string | number, shouldAutoExpandParent = true) => {
	treeRef.value!.setCurrentKey(key, shouldAutoExpandParent);
};
//设置节点为选中状态，使用此方法必须设置 node-key 属性
//(node, shouldAutoExpandParent=true) 1. 待被选中的节点 2. 是否展开父节点
const setCurrentNode = (node: any, shouldAutoExpandParent = true) => {
	treeRef.value!.setCurrentNode(node, shouldAutoExpandParent);
};
//	根据 data 或者 key 拿到 Tree 组件中的 node
//(data) 节点的 data 或 key
const getNode = (data: any) => {
	return treeRef.value!.getNode(data);
};
//删除 Tree 中的一个节点，使用此方法必须设置 node-key 属性
//(data) 要删除的节点的 data 或者 node 对象
const remove = (node: any) => {
	treeRef.value!.remove(node);
};
//为 Tree 中的一个节点追加一个子节点
//(data, parentNode) 1. 要追加的子节点的 data 2. 父节点的 data, key 或 node
const append = (data: any, parentNode: any) => {
	treeRef.value!.append(data, parentNode);
};
//在 Tree 中给定节点前插入一个节点
//(data, refNode) 1. 要增加的节点的 data 2. 参考节点的 data, key 或 node
const insertBefore = (data: any, refNode: any) => {
	treeRef.value!.insertBefore(data, refNode);
};
//在 Tree 中给定节点后插入一个节点
//(data, refNode) 1. 要增加的节点的 data 2. 参考节点的 data, key 或 node
const insertAfter = (data: any, refNode: any) => {
	treeRef.value!.insertAfter(data, refNode);
};
//将方法暴露出去
defineExpose({
	updateKeyChildren,
	getCheckedNodes,
	setCheckedNodes,
	getCheckedKeys,
	setCheckedKeys,
	setChecked,
	getHalfCheckedNodes,
	getHalfCheckedKeys,
	getCurrentKey,
	getCurrentNode,
	setCurrentKey,
	setCurrentNode,
	getNode,
	remove,
	append,
	insertBefore,
	insertAfter,
});
onMounted(() => {
  nextTick(()=>{
    query();
  })
});
</script>
<style lang="scss" scoped>
:deep(.el-tree-node__content) {
	height: 30px;
}
/***
:deep(.el-select-dropdown) {
	padding-left: 0px;
} */

:deep(.el-select-dropdown__item) {
	position: static;
}
.search-tree-color {
	color: var(--el-color-primary);
}
</style>
